import { Progress } from '../components/progress/progress';
import { DICT } from '../dict/dict';

function httpError(code: number): string {
	if (code === 0) return DICT.networkError;
	if (code >= 400 && code < 500) return DICT.badRequest;
	return DICT.serverError;
}

function getResponseError(xhr: XMLHttpRequest): string {
	const contentType = xhr.getResponseHeader('Content-Type') ?? '';
	if (/^text\/plain(\s*;.*)?$/.test(contentType.trim())) {
		return xhr.responseText;
	} else {
		return `${xhr.status}: ${httpError(xhr.status)}`;
	}
}

function isResponseJson(xhr: XMLHttpRequest): boolean {
	const contentType = xhr.getResponseHeader('Content-Type') ?? '';
	return /^application\/json(\s*;.*)?$/.test(contentType.trim());
}

type UploadInfo = {
	name: string;
	size: number;
	path: string;
};
export function uploadFiles(
	url: string,
	fileList: FileList | File[],
	extraData?: Record<string, string>
): Promise<UploadInfo[]> {
	return new Promise<UploadInfo[]>((resolve, reject) => {
		Progress.show();
		const fd = new FormData();
		for (let i = 0; i < fileList.length; i++) {
			const file = fileList[i];
			if (file) {
				fd.append('file', file, encodeURIComponent(file.name));
			}
		}
		if (extraData) {
			for (const key in extraData) {
				if (Object.hasOwn(extraData, key)) {
					fd.append(key, extraData[key]);
				}
			}
		}
		const xhr = new XMLHttpRequest();
		xhr.upload.addEventListener(
			'progress',
			(e) => {
				Progress.update(e.loaded / e.total, DICT.uploading);
			},
			false
		);
		xhr.addEventListener(
			'load',
			() => {
				if (xhr.status === 200) {
					Progress.close();
					if (isResponseJson(xhr)) {
						resolve(JSON.parse(xhr.responseText));
					} else {
						reject(DICT.unexpectedServerResponse);
					}
				} else if (xhr.status === 204) {
					Progress.close();
					resolve([]);
				} else {
					Progress.close();
					const errMsg = getResponseError(xhr);
					reject(errMsg);
				}
			},
			false
		);
		xhr.addEventListener(
			'error',
			() => {
				Progress.close();
				reject(DICT.networkError);
			},
			false
		);
		xhr.addEventListener(
			'abort',
			() => {
				Progress.close();
				reject(DICT.aborted);
			},
			false
		);
		try {
			xhr.open('POST', url);
			xhr.send(fd);
		} catch (e) {
			Progress.close();
			reject(e + '');
		}
	});
}

export function readFileAsDataURL(file: File): Promise<string> {
	return new Promise<string>((resolve, reject) => {
		const reader = new FileReader();
		reader.addEventListener('load', () => {
			if (typeof reader.result === 'string') {
				resolve(reader.result);
			} else {
				reject('cannot read as data url');
			}
		});
		reader.addEventListener('error', () => {
			reject(reader.error ? reader.error + '' : 'cannot read as data url');
		});
		reader.readAsDataURL(file);
	});
}

export function readFilesAsDataURL(fileList: FileList | File[]): Promise<string[]> {
	return Promise.all(
		Array.from(fileList).map((file) => {
			return readFileAsDataURL(file);
		})
	);
}

export function downloadDataURL(dataUrl: string, fileName: string) {
	const a = document.createElement('a');
	a.href = dataUrl;
	a.download = fileName;
	a.click();
}

export async function downloadFile(file: File): Promise<void> {
	const url = await readFileAsDataURL(file);
	downloadDataURL(url, file.name);
}

type SelectFileOptions = {
	mimeType?: string;
	multiple?: boolean;
	filter?: (file: File) => boolean;
};

/**
 * Select files from file system.
 * @param options
 * @returns Promise<File[]>
 */
export function selectFiles(options: SelectFileOptions = {}): Promise<File[]> {
	return new Promise<File[]>((resolve) => {
		const ipt = document.createElement('input');
		ipt.type = 'file';
		ipt.accept = options.mimeType ?? '*/*';
		ipt.multiple = options.multiple ?? false;
		ipt.click();
		ipt.oncancel = () => {
			resolve([]);
		};
		ipt.onchange = () => {
			const fileList = ipt.files;
			if (fileList === null) {
				resolve([]);
				return;
			}
			let files: File[];
			if (options.filter) {
				files = Array.from(fileList).filter(options.filter);
			} else {
				files = Array.from(fileList);
			}
			resolve(files);
		};
	});
}
